package com.baitiaojun.video.service.impl;

import com.baitiaojun.api.domain.user.UserPreference;
import com.baitiaojun.api.domain.video.Video;
import com.baitiaojun.api.domain.video.VideoComment;
import com.baitiaojun.api.domain.video.VideoView;
import com.baitiaojun.api.exception.ConditionException;
import com.baitiaojun.video.config.AsyncConfig;
import com.baitiaojun.video.mapper.VideoMapper;
import com.baitiaojun.video.mapper.VideoOperationMapper;
import com.baitiaojun.video.mapper.VideoViewMapper;
import com.baitiaojun.video.service.VideoParentCommentService;
import com.baitiaojun.video.service.VideoRecommendService;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericPreference;
import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @类名 VideoRecommendServiceImpl
 * @描述 TODO
 * @作者 白条君
 * @创建日期 2023/1/6 17:45
 * @版本 1.0
 */
@Service
public class VideoRecommendServiceImpl implements VideoRecommendService {

    @Resource
    private VideoMapper videoMapper;

    @Resource
    private VideoViewMapper videoViewMapper;

    @Autowired
    private VideoParentCommentService videoParentCommentService;

    @Resource
    private VideoOperationMapper videoOperationMapper;

    @Autowired
    private AsyncConfig asyncConfig;

    /**
     * 根据数据库中历史操作记录获取所有用户的偏好
     * 数据降噪（根据用户偏好整合出有效数据对象）
     * 归一化（不同评分机制和偏好的数据进行统一计算）从而得到当前用户与其他用户的相似度
     * TODO 待优化，以后改用hadoop
     * @param userId
     * @return
     */
    @Override
    public List<Video> recommend(Long userId) throws TasteException, InterruptedException, ConditionException {
        Set<Long> videoIds = this.genericRecommendVideoIds(userId);
        if (videoIds.size() == 0) {
            return null;
        }
        AtomicReference<List<Video>> videos = new AtomicReference<>();
        //获取线程池执行器
        Executor executor = asyncConfig.getAsyncExecutor();
        if (ObjectUtils.isEmpty(executor)) {
            throw new ConditionException("无法获取线程池执行器");
        }
        //设置等待计时器
        CountDownLatch latch = new CountDownLatch(3);
        if (ObjectUtils.isEmpty(latch)) {
            throw new ConditionException("无法获取倒计时计数器");
        }
        //根据videoIds批量查找出video列表
        executor.execute(() -> {
            videos.set(videoMapper.batchSelectVideos(videoIds));
            latch.countDown();
        });
        //批量统计观看次数
        AtomicReference<List<VideoView>> videoViews = new AtomicReference<>();
        executor.execute(() -> {
            videoViews.set(videoViewMapper.batchVideoViewCount(videoIds));
            latch.countDown();
        });
        //批量统计评论条数
        AtomicReference<List<VideoComment>> videoComments = new AtomicReference<>();
        executor.execute(() -> {
            videoComments.set(videoParentCommentService.batchVideoCommentCount(videoIds));
            latch.countDown();
        });
        latch.await();
        //将每个视频的观看次数和评论条数进行对应的赋值
        for (Video video : videos.get()) {
            for (VideoView videoView : videoViews.get()) {
                if (video.getId().equals(videoView.getVideoId())) {
                    video.setViewCount(videoView.getCount());
                }
            }
            for (VideoComment videoComment : videoComments.get()) {
                if (video.getId().equals(videoComment.getVideoId())) {
                    video.setCommentCount(videoComment.getCount());
                }
            }
        }
        return videos.get();
    }

    private Set<Long> genericRecommendVideoIds(Long userId) throws TasteException {
        //数据库中根据用户历史操作得到偏好记录的list
        List<UserPreference> userPreferences = videoOperationMapper.selectUserPreferences();
        //数据降噪，创建所有用户偏好记录的数据模型，方便后续操作
        DataModel dataModel = this.genericDataModel(userPreferences);
        //指定通过余弦定理对数据模型计算，获取所有用户之间的相似程度
        UserSimilarity similarity = new UncenteredCosineSimilarity(dataModel);
        //通过余弦定理对数据模型计算得到最相似的K个邻居(参数是邻居数量，相似程度的数据，数据模型)
        NearestNUserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
        //创建推荐器
        GenericUserBasedRecommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
        //获取给当前用户推荐的视频列表的id
        List<RecommendedItem> recommendedItems = recommender.recommend(userId, 10);
        //取出视频列表中的videoId，转换为视频的id列表
        return recommendedItems.stream().map(RecommendedItem::getItemID).collect(Collectors.toSet());
    }

    /**
     * 创建所有用户偏好记录的数据模型，方便后续操作
     * @param userPreferences
     */
    private DataModel genericDataModel(List<UserPreference> userPreferences) {
        //创建fastbyidmap对象用来存储PreferenceArray
        FastByIDMap<PreferenceArray> fastByIDMap = new FastByIDMap<>();
        //用户偏好列表转换为map，将每一个用户的userid和他对应的偏好列表进行映射
        Map<Long, List<UserPreference>> userPreferenceMap
                = userPreferences.stream().collect(Collectors.groupingBy(UserPreference::getUserid));
        //遍历这个map的所有values
        Collection<List<UserPreference>> userPreferenceCollection = userPreferenceMap.values();
        GenericPreference[] genericPreferences;
        UserPreference userPreference;
        GenericPreference genericPreference;
        for (List<UserPreference> preferences : userPreferenceCollection) {
            int size = preferences.size();
            //创建推荐引擎下的GenericPreference数组
            genericPreferences = new GenericPreference[size];
            //根据下标遍历每一个preferences列表
            for (int i = 0; i < size; i++) {
                userPreference = preferences.get(i);
                //构建GenericPreference对象，将userid，videoid，value作为参数传递给推荐引擎
                genericPreference = new GenericPreference(userPreference.getUserid(), userPreference.getVideoid(), userPreference.getScore());
                //将genericperference对象添加到GenericPreference数组
                genericPreferences[i] = genericPreference;
            }
            //将userid作为key，GenericUserPreferenceArray对象作为value，将genericPreferences作为参数传递纯递给GenericUserPreferenceArray对象
            fastByIDMap.put(genericPreferences[0].getUserID(), new GenericUserPreferenceArray(Lists.newArrayList(genericPreferences)));
        }
        //创建GenericDataModel实例返回
        return new GenericDataModel(fastByIDMap);
    }
}
